package main

import (
	"fmt"
	"runtime"
	"sync"
	"time"
)

func main() {
	//所谓的goroutine泄露就是goroutine永远无法退出的情况
	//以下是几种常见的导致泄露的情况

	//case1()

	//case2()
	resolveCase2()

	//case3()

	//case4()

	//case5()

	//case6()

}

func case1() {
	//情况1：因为timer的stop并不会关闭其channel，导致无法退出
	t := time.NewTimer(2 * time.Second)
	go func() {
		for {
			select {
			case <-t.C:
				fmt.Println("计时器停止，退出协程")
				return
			default:
				fmt.Println("等待计时器停止")
				time.Sleep(1 * time.Second)
			}
		}
	}()

	fmt.Println("1秒后stop计时器")
	time.Sleep(1 * time.Second)
	//stop并不会关闭t.C 且由于stop之后，timer不会再过期，导致t.C永远阻塞
	t.Stop()
	//可以通过重置timer来达到目的
	//t.Reset(0)
	fmt.Println("已stop，等待协程退出")
	time.Sleep(5 * time.Second)
	fmt.Println("当前协程数量：", runtime.NumGoroutine()) //这里数量是2，因为有泄露
}

func case2() {
	//情况2：通道只有写入没有接收
	//如下：接收方已退出，而channel满或本身无缓存导致无法发送，从而阻塞协程以致无法退出
	ch := make(chan struct{})
	go func() {
		time.Sleep(2 * time.Second)
		fmt.Println("2秒到了，向channel写入")
		ch <- struct{}{}
		fmt.Println("写入完毕") //这里不会输出，因为上一行永远阻塞了
	}()

	select {
	case <-ch:
		fmt.Println("读取信息")
	case <-time.After(1 * time.Second):
		fmt.Println("超时，退出")
	}

	time.Sleep(4 * time.Second)
	fmt.Println("当前协程数量：", runtime.NumGoroutine()) //这里数量是2，因为有泄露
	//解决办法见：resolveCase2
}

func resolveCase2() {
	//解决办法：当接收方退出时，通知发送方也退出即可
	ch := make(chan struct{})
	done := make(chan struct{})
	go func() {
		time.Sleep(2 * time.Second)
		fmt.Println("2秒到了，尝试向channel写入")
		select {
		case ch <- struct{}{}:
			fmt.Println("写入完毕")
		case <-done:
			fmt.Println("外部结束")
			return
		}
	}()

	select {
	case <-ch:
		fmt.Println("读取信息")
	case <-time.After(1 * time.Second):
		fmt.Println("超时，关闭done通道")
		close(done)
	}

	time.Sleep(4 * time.Second)
	fmt.Println("当前协程数量：", runtime.NumGoroutine()) //这里数量是1
}

func case3() {
	//情况3：死循环 协程无法退出
	go func() {
		for {
			time.Sleep(1 * time.Second)
		}
	}()
	time.Sleep(2 * time.Second)
	fmt.Println("当前协程数量：", runtime.NumGoroutine()) //这里数量是2，因为有泄露
}

func case4() {
	//情况4：nil channel
	//对为nil的channel进行发送和接收操作都会阻塞
	defer func() {
		time.Sleep(time.Second)
		fmt.Println("当前协程数量：", runtime.NumGoroutine()) //这里数量是2，因为有泄露
	}()

	var ch chan struct{}
	//<-ch 不能在同一个协程里进行该操作，因为无相应发送者，直接死锁

	go func() {
		fmt.Println("进行接收")
		<-ch                //阻塞在这里
		fmt.Println("接收完毕") //不输出
	}()

	time.Sleep(5 * time.Second)
}

func case5() {
	//情况5：因为加锁还未解锁，导致阻塞
	lock := sync.Mutex{}
	go func() {
		for i := 0; i < 2; i++ {
			lock.Lock()
			fmt.Println("输出") //只会输出一次

			//解决方法：
			//1.适用于加锁和解锁之间不可能异常的情况
			//lock.Lock()
			//fmt.Println("输出")
			//lock.Unlock()

			//2.有可能异常需要用defer，但是又不能再循环里用，因为只有彻底退出循环才算结束方法体，defer才会调用
			//通过匿名函数立即执行达到目的
			//func() {
			//	lock.Lock()
			//	defer lock.Unlock()
			//	fmt.Println("输出")
			//}()
		}
	}()
	time.Sleep(3 * time.Second)
	fmt.Println("当前协程数量：", runtime.NumGoroutine()) //这里数量是2，因为有泄露
}

func case6() {
	//WaitGroup的Add数量超过Done数量导致不退出的问题
	go case6Sub()
	time.Sleep(2 * time.Second)
	fmt.Println("当前协程数量：", runtime.NumGoroutine()) //这里数量是2，因为有泄露
}

func case6Sub() {
	wg := sync.WaitGroup{}
	wg.Add(3)
	go func() {
		time.Sleep(1 * time.Second)
		wg.Done()
	}()
	go func() {
		time.Sleep(1 * time.Second)
		wg.Done()
	}()
	fmt.Println("等待退出")
	wg.Wait()          //永远等待
	fmt.Println("已退出") //不会执行
}
